﻿using System;
using System.Collections.Generic;

namespace BizElements.TemplateEngine
{
    /// <summary>A component passed as input parameter to a template or template segment.</summary>
    /// <remarks>Properties and methods of ITemplateInputComponent are accessible to template expressions (see <see cref="ITemplateExpression"/>) 
    /// used in template XML files via <see cref="ITemplateExpression.Input"/> property.</remarks>
    public interface ITemplateInputComponent
    {
        #region Properties.

        /// <summary>Component name.</summary>
        string Name { get; }

        /// <summary>.NET type of the value stored in Instance property.</summary>
        Type Class { get; }

        /// <summary>Component instance.</summary>
        object Instance { get; }

        /// <summary>Type of component stored in Instance property.</summary>
        ComponentType Type { get; }

        /// <summary>Parent component of the current instance or null.</summary>
        ITemplateInputComponent Parent { get; }

        /// <summary>Root component of the hierarchy to which the current instance belongs to.</summary>
        ITemplateInputComponent Root { get; }

        /// <summary>Optional data for template that is currently running.</summary>
        /// <remarks>Enables components to access template variables and settings.</remarks>
        ITemplateData CurrentTemplateData { get; set; }

        /// <summary>Gets unique component name.</summary>
        string FullName { get; }

        #endregion

        #region GetChildren().

        /// <summary>Gets all subcomponents.</summary>
        IEnumerable<ITemplateInputComponent> GetChildren();

        /// <summary>Gets all subcomponents of the specified type.</summary>
        IEnumerable<ITemplateInputComponent> GetChildren(ComponentType type);

        /// <summary>Gets all subcomponents of the specified type and belong to the specified range.</summary>
        IEnumerable<ITemplateInputComponent> GetChildren(ComponentType type, int startAt, int maxCount);

        /// <summary>Gets all subcomponents matching specified criteria.</summary>
        IEnumerable<ITemplateInputComponent> GetChildren(params Predicate<ITemplateInputComponent>[] criteria);

        /// <summary>Gets all subcomponents matching specified criteria and belong to the specified range.</summary>
        IEnumerable<ITemplateInputComponent> GetChildren(int startAt, int maxCount, params Predicate<ITemplateInputComponent>[] criteria);

        /// <summary>Gets all subcomponents of the specified type matching specified criteria.</summary>
        IEnumerable<ITemplateInputComponent> GetChildren(ComponentType type, params Predicate<ITemplateInputComponent>[] criteria);

        /// <summary>Gets all subcomponents of the specified type matching specified criteria and which belong to the specified range.</summary>
        IEnumerable<ITemplateInputComponent> GetChildren(ComponentType type, int startAt, int maxCount, params Predicate<ITemplateInputComponent>[] criteria);

        #endregion

        #region ExistsChild().

        /// <summary>Checks whether the current object has any subcomponents.</summary>
        bool ExistsChild();

        /// <summary>Checks whether the current object has subcomponents of the specified type.</summary>
        bool ExistsChild(ComponentType type);

        /// <summary>Checks whether the current object has subcomponents matching specified criteria.</summary>
        bool ExistsChild(params Predicate<ITemplateInputComponent>[] criteria);

        /// <summary>Checks whether the current object has of the specified type matching specified criteria.</summary>
        bool ExistsChild(ComponentType type, params Predicate<ITemplateInputComponent>[] criteria);

        #endregion

        #region Component variables - GetComponentVariable() and ComponentVariables getter.

        /// <summary>Gets the value of the specified component variable/metadata if it exists in <see cref="BizElements.TemplateEngine.ITemplateData.AllComponentVariables"/>.</summary>
        /// <param name="componentVariableName">Component variable name.</param>
        /// <returns>Variable name if found; otherwise <b>null</b></returns>
        string GetComponentVariable(string componentVariableName);

        /// <summary>Input component variables/metadata.</summary>
        ComponentVariableSet ComponentVariables { get; }

        #endregion        
    }

    /// <summary>Base class from which components passed as input parameter to a template or template segment are derived.</summary>
    public abstract class TemplateInputComponentBase : ITemplateInputComponent
    { 
        #region CTor.

        /// <summary>Initializes a new instance of TemplateInputComponentBase class.</summary>
        protected TemplateInputComponentBase(ComponentType type, object obj, string name, string fullName, ITemplateInputComponent parent)
        {
            this.Type = type;
            this.Instance = obj;
            this.Class = obj.GetType();
            this.Name = name ?? this.Class.Name;
            this.FullName = fullName ?? this.Name;
            this.Parent = parent;
            this.Root = (parent != null) ? parent.Root : this;
        }

        #endregion

        #region ITemplateInputComponent properties.

        /// <summary>Component name.</summary>
        public string Name { get; private set; }

        /// <summary>Unique component name.</summary>
        public string FullName { get; private set; }

        /// <summary>.NET type of the value stored in Instance property.</summary>
        public Type Class { get; private set; }

        /// <summary>Component instance.</summary>
        public object Instance { get; private set; }

        /// <summary>Type of component stored in Instance property.</summary>
        public ComponentType Type { get; private set; }

        /// <summary>Parent component of the current instance or null.</summary>
        public ITemplateInputComponent Parent { get; private set; }

        /// <summary>Root component of the hierarchy to which the current instance belongs to.</summary>
        public ITemplateInputComponent Root { get; private set; }

        ITemplateData currentTemplateData;

        /// <summary>Optional template that is currently running.</summary>
        /// <remarks>Enables components to access template variables and settings.</remarks>
        public ITemplateData CurrentTemplateData
        {
            get
            {
                if (this.currentTemplateData != null)
                    return this.currentTemplateData;
                else if (this.Root != null)
                    return this.Root.CurrentTemplateData;
                else
                    return null;
            }
            set
            {
                this.currentTemplateData = value;
            }
        }

        #endregion

        #region ITemplateInputComponent GetChildren() methods.

        /// <summary>Gets all subcomponents.</summary>
        public IEnumerable<ITemplateInputComponent> GetChildren()
        {
            EnsureSubcomponents();
            return this.Subcomponents;
        }

        /// <summary>Gets all subcomponents of the specified type.</summary>
        public IEnumerable<ITemplateInputComponent> GetChildren(ComponentType type)
        {
            return ArrayUtil.FindAll<List<ITemplateInputComponent>, ITemplateInputComponent>(GetChildren(), (comp) => (comp.Type == type));
        }

        /// <summary>Gets all subcomponents of the specified type and belong to the specified range.</summary>
        public IEnumerable<ITemplateInputComponent> GetChildren(ComponentType type, int startAt, int maxCount)
        {
            var allMatches = ArrayUtil.FindAll<List<ITemplateInputComponent>, ITemplateInputComponent>(GetChildren(), (comp) => (comp.Type == type));
            return ArrayUtil.GetRange<ITemplateInputComponent>(allMatches, startAt, maxCount);
        }

        /// <summary>Gets all subcomponents matching specified criteria.</summary>
        public IEnumerable<ITemplateInputComponent> GetChildren(params Predicate<ITemplateInputComponent>[] criteria)
        {
            var allChildren = GetChildren();
            if (ArrayUtil.IsNullOrEmpty(criteria))
                return allChildren;
            else
                return Template.GetMatchingComponents(allChildren, criteria);
        }

        /// <summary>Gets all subcomponents matching specified criteria and belong to the specified range.</summary>
        public IEnumerable<ITemplateInputComponent> GetChildren(int startAt, int maxCount, params Predicate<ITemplateInputComponent>[] criteria)
        {
            var allChildren = GetChildren();
            IEnumerable<ITemplateInputComponent> allMatches;
            if (ArrayUtil.IsNullOrEmpty(criteria))
                allMatches = allChildren;
            else
                allMatches = Template.GetMatchingComponents(allChildren, criteria);

            return ArrayUtil.GetRange<ITemplateInputComponent>(allMatches, startAt, maxCount);
        }

        /// <summary>Gets all subcomponents of the specified type matching specified criteria.</summary>
        public IEnumerable<ITemplateInputComponent> GetChildren(ComponentType type, params Predicate<ITemplateInputComponent>[] criteria)
        {
            return Template.GetMatchingComponents(GetChildren(type), criteria);
        }

        /// <summary>Gets all subcomponents of the specified type matching specified criteria and which belong to the specified range.</summary>
        public IEnumerable<ITemplateInputComponent> GetChildren(ComponentType type, int startAt, int maxCount, params Predicate<ITemplateInputComponent>[] criteria)
        {
            var allMatches = Template.GetMatchingComponents(GetChildren(type), criteria);
            return ArrayUtil.GetRange<ITemplateInputComponent>(allMatches, startAt, maxCount);
        }

        #endregion

        #region ExistsChild().

        /// <summary>Checks whether the current object has any subcomponents.</summary>
        public bool ExistsChild()
        {
            EnsureSubcomponents();
            return !ArrayUtil.IsNullOrEmpty(this.Subcomponents);
        }

        /// <summary>Checks whether the current object has subcomponents of the specified type.</summary>
        public bool ExistsChild(ComponentType type)
        {
            EnsureSubcomponents();
            return ArrayUtil.Exists<ITemplateInputComponent>(GetChildren(), (comp) => (comp.Type == type));
        }

        /// <summary>Checks whether the current object has subcomponents matching specified criteria.</summary>
        public bool ExistsChild(params Predicate<ITemplateInputComponent>[] criteria)
        {
            EnsureSubcomponents();
            if (ArrayUtil.IsNullOrEmpty(criteria))
                return ExistsChild();

            foreach (ITemplateInputComponent component in this.Subcomponents)
            {
                if (Template.MatchesCriteria(component, criteria))
                    return true;
            }

            return false;
        }

        /// <summary>Checks whether the current object has of the specified type matching specified criteria.</summary>
        public bool ExistsChild(ComponentType type, params Predicate<ITemplateInputComponent>[] criteria)
        {
            EnsureSubcomponents();
            if (ArrayUtil.IsNullOrEmpty(criteria))
                return ExistsChild(type);

            foreach (ITemplateInputComponent component in this.Subcomponents)
            {
                if ((component.Type == type) && Template.MatchesCriteria(component, criteria))
                    return true;
            }

            return false;
        }

        #endregion

        #region Subcomponents - partial implementation. Must implement EnsureSubcomponents() in derived classes.

        /// <summary>Gets or sets subcomponent list.</summary>
        protected List<ITemplateInputComponent> Subcomponents { get; set; }

        /// <summary>Ensures that <see cref="Subcomponents"/> property is initialized. Must be overriden in derived classes.</summary>
        protected abstract void EnsureSubcomponents();

        #endregion

        #region Component variables - partial implementation. Must implement GetComponentKey() in derived classes.

        /// <summary>Gets component keys that are used it in template's <see cref="BizElements.TemplateEngine.ITemplateData.AllComponentVariables"/> dictionary. These represent 1st level key.</summary>
        /// <returns>Keys that identify source objects from which metadata for current input component is based on. Eg. database table, business object class etc.</returns>
        protected abstract string[] GetComponentKeys();

        /// <summary>Gets the value of the specified component variable/metadata if it exists in <see cref="BizElements.TemplateEngine.ITemplateData.AllComponentVariables"/>.</summary>
        /// <param name="componentVariableName">Component variable name.</param>
        /// <returns>Variable name if found; otherwise <b>null</b></returns>
        public string GetComponentVariable(string componentVariableName)
        {
            if (this.CurrentTemplateData == null || this.CurrentTemplateData.AllComponentVariables == null || this.CurrentTemplateData.AllComponentVariables.Count == 0)
                return null;

            string[] componentKeys = GetComponentKeys();
            if (componentKeys == null)
                return null;

            foreach (string key in componentKeys)
            {
                IDictionary<string, string> componentVariables;
                this.CurrentTemplateData.AllComponentVariables.TryGetValue(key, out componentVariables);
                if ((componentVariables != null) && componentVariables.ContainsKey(componentVariableName))
                    return componentVariables[componentVariableName];
            }

            return null;
        }

        ComponentVariableSet componentVariables;

        /// <summary>Input component variables/metadata.</summary>
        public ComponentVariableSet ComponentVariables
        {
            get
            {
                if (this.componentVariables == null)
                    this.componentVariables = new ComponentVariableSet(this);

                return this.componentVariables;
            }
        }

        #endregion
    }

    /// <summary>Represents a read-only collection of key/value pairs where key represent variable name and value represents textual variable value returned by underlying input component.</summary>
    public sealed class ComponentVariableSet
    {
        #region CTor and indexer.

        ITemplateInputComponent component;

        /// <summary>Initializes a new instance of <b>ComponentVariableSet</b> class.</summary>
        /// <param name="component">Input component whose variables are wrapped inside current isntance.</param>
        public ComponentVariableSet(ITemplateInputComponent component)
        {
            this.component = component;
        }

        /// <summary>Tries to get variable with the specified name.</summary>
        /// <param name="name">Variable name.</param>
        public string this[string name]
        {
            get { return this.component.GetComponentVariable(name) ?? ""; }
        }

        #endregion
    }
}